#include <rid.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fn85.h>
#include <rid_fn85.h>

#include <unfy.h>

#include "../aha.h"

#define pass(str) (sizeof(str) - 1)

void
print_term(const Unfy_term *term);

void
print_list(const Unfy_list *list)
{
	printf("[");

	print_term(list->term);
	list = list->next;

	for (; list; list = list->next) {
		printf(", ");
		print_term(list->term);
	}

	printf("]");
}

void
print_term(const Unfy_term *term)
{
	Unfy_list *list;
	Rid_fn85 str;

	switch (term->type) {
	case UNFY_CONST:
		printf("Const ");
		break;
	case UNFY_LIST:
		print_list(term->u.list);
		return;
	case UNFY_VAR:
		printf("Var ");
		break;
	}

	rid_fn85(str, term->u.id);
	printf("%s", str);
}

void
print_bind(const Unfy_bind *bind)
{
	Rid_fn85 str;

	for (; bind; bind = bind->next) {
		rid_fn85(str, bind->var_id);
		printf("\tVar %s -> ", str);
		print_term(bind->term);
		printf("\n");
	}
}

Unfy_term *
parse_term(const char *str, size_t *pos, Unfy_recycle *rec);

Unfy_term *
parse_list(const char *str, size_t *pos, Unfy_recycle *rec)
{
	int first = 1;
	Unfy_list *head = NULL;
	Unfy_list **tail = &head;
	Unfy_term *list;

	while (*str != ']') {
		size_t old;
		Unfy_term *term;

		if (!first) {
			if (strncmp(str, ", ", pass(", "))) {
				printf("Missing a comma and space at %zu\n", *pos);
				unfy_recycle_list(rec, head);
				return NULL;
			}

			str += pass(", ");
			*pos += pass(", ");
		} else {
			first = 0;
		}

		old = *pos;

		if (!(term = parse_term(str, pos, rec))) {
			unfy_recycle_list(rec, head);
			return NULL;
		}

		str += *pos - old;

		if (!(tail = unfy_list_append(tail, term, rec))) {
			printf("Error appending to list at: %zu\n", *pos);
			unfy_recycle_term(rec, term);
			unfy_recycle_list(rec, head);
			return NULL;
		}
	}

	++*pos;

	if (!(list = unfy_term(UNFY_LIST, head, rec))) {
		printf("Error creating list at: %zu\n", *pos);
		unfy_recycle_list(rec, head);
	}

	return list;
}

Unfy_term *
parse_term(const char *str, size_t *pos, Unfy_recycle *rec)
{
	Unfy_type type;
	Rid id;
	size_t chars;
	Unfy_term *term;

	switch (*str) {
	case '[':
		++*pos;
		return parse_list(str + 1, pos, rec);
	case 'V':
		if (strncmp(str, "Var ", pass("Var "))) {
			printf("Invalid type at: %zu\n", *pos);
			return NULL;
		}

		*pos += pass("Var ");
		str += pass("Var ");
		type = UNFY_VAR;
		break;
	case 'C':
		if (strncmp(str, "Const ", pass("Const "))) {
			printf("Invalid type at: %zu\n", *pos);
			return NULL;
		}

		*pos += pass("Const ");
		str += pass("Const ");
		type = UNFY_CONST;
		break;
	}

	switch (rid_fn85_parse(id, str, &chars)) {
	case FN85_OKAY:
		*pos += chars;
		term = unfy_term(type, id, rec);

		if (!term)
			printf("Error allocating term at: %zu\n",
			       *pos + chars);

		return term;
	case FN85_INVALID:
		printf("Something wasn't a digit at %zu\n",
		       *pos + chars);

		return NULL;
	case FN85_TOO_HIGH:
		printf("Number became larger than possible at %zu\n",
		       *pos + chars);

		return NULL;
	}
}

typedef enum { ASSERT, QUERY, ERROR } Command;

Command
get_command(const char *str, size_t *pos)
{
	switch (*str) {
	case 'A':
		if (strncmp(str, "Assert ", pass("Assert ")))
			break;

		*pos += pass("Assert ");
		return ASSERT;
	case 'Q':
		if (strncmp(str, "Query ", pass("Query ")))
			break;

		*pos += pass("Query ");
		return QUERY;
	}

	printf("Invalid command at: %zu\n", *pos);
	return ERROR;
}

void
query(Unfy_list *list, const Unfy_term *term, Unfy_recycle *rec)
{
	while (1) {
		Unfy_bind *lbind = NULL;
		Unfy_bind *rbind = NULL;

		switch (aha_list_query(&list, term, &lbind, &rbind, rec)) {
		case UNFY_YES:
			printf("\nyes: ");
			print_term(list->term);
			printf("\n");

			if (lbind) {
				printf("\nlbinds:\n");
				print_bind(lbind);
				unfy_recycle_bind(rec, lbind);
			}

			if (rbind) {
				printf("\nrbinds:\n");
				print_bind(rbind);
				unfy_recycle_bind(rec, rbind);
			}

			list = list->next;
			break;
		case UNFY_NO:
			printf("\nno\n\n");
		        return;
		case UNFY_ERR:
			fprintf(stderr,
				"Error: failed to query term\n");
		        return;
		}
	}
}

void
repl(void)
{
	Unfy_recycle rec;
	Unfy_list *list = NULL;
	char *str = NULL;
	size_t n = 0;

	unfy_recycle_init(&rec);

	while (1) {
		size_t pos = 0;
		Command com;
		Unfy_term *term;

		printf("* ");

		if (getline(&str, &n, stdin) < 0) {
			printf("Closing repl\n");
			break;
		}

		if ((com = get_command(str, &pos)) == ERROR)
			continue;

		if (!(term = parse_term(str + pos, &pos, &rec)))
			continue;

		switch (com) {
		case ASSERT:
			if (aha_list_add(&list, term, &rec) < 0) {
				fprintf(stderr,
					"Error: failed to assert term\n");
				unfy_recycle_term(&rec, term);
			} else {
				print_list(list);
				printf("\n");
			}

			break;
		case QUERY:
			query(list, term, &rec);
			unfy_recycle_term(&rec, term);
			break;
		}
	}

	if (list)
		unfy_recycle_list(&rec, list);

	unfy_recycle_empty(&rec);
	free(str);
}

int
main(int argc, char *argv[])
{
	repl();
	return 0;
}
